/*******************************************************************************
 * Copyright (c) 2009, 2016 Wind River Systems and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     Wind River Systems - initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.tests.dsf;

import static org.junit.Assert.fail;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.cdt.dsf.ui.viewmodel.properties.IPropertiesUpdate;
import org.eclipse.cdt.dsf.ui.viewmodel.properties.IPropertiesUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.ILabelUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenCountUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IHasChildrenUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ILabelUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelChangedListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelDelta;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IModelProxy;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IStateUpdateListener;
import org.eclipse.debug.internal.ui.viewers.model.provisional.ITreeModelViewer;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdate;
import org.eclipse.debug.internal.ui.viewers.model.provisional.IViewerUpdateListener;
import org.eclipse.jface.viewers.TreePath;

/**
 * @since 2.2
 */
public class ViewerUpdatesListener 
    implements IViewerUpdateListener, ILabelUpdateListener, IModelChangedListener, IViewerUpdatesListenerConstants,
        IStateUpdateListener, IPropertiesUpdateListener
{
    private ITreeModelViewer fViewer;
    
    private boolean fFailOnRedundantUpdates;
    private Set<IViewerUpdate> fRedundantUpdates = new HashSet<IViewerUpdate>();
    
    private boolean fFailOnMultipleModelUpdateSequences;
    private boolean fMultipleModelUpdateSequencesObserved;
    private boolean fFailOnMultipleLabelUpdateSequences;
    private boolean fMultipleLabelUpdateSequencesObserved;
    
    private Set<TreePath> fHasChildrenUpdatesScheduled = makeTreePathSet();
    private Set<IViewerUpdate> fHasChildrenUpdatesRunning = new HashSet<IViewerUpdate>();
    private Set<IViewerUpdate> fHasChildrenUpdatesCompleted = new HashSet<IViewerUpdate>();
    private Map<TreePath, Set<Integer>> fChildrenUpdatesScheduled = makeTreePathMap();
    private Set<IViewerUpdate> fChildrenUpdatesRunning = new HashSet<IViewerUpdate>();
    private Set<IViewerUpdate> fChildrenUpdatesCompleted = new HashSet<IViewerUpdate>();
    private Set<TreePath> fChildCountUpdatesScheduled = makeTreePathSet();
    private Set<IViewerUpdate> fChildCountUpdatesRunning = new HashSet<IViewerUpdate>();
    private Set<IViewerUpdate> fChildCountUpdatesCompleted = new HashSet<IViewerUpdate>();
    private Set<TreePath> fLabelUpdates = makeTreePathSet();
    private Set<IViewerUpdate> fLabelUpdatesRunning = new HashSet<IViewerUpdate>();
    private Set<IViewerUpdate> fLabelUpdatesCompleted = new HashSet<IViewerUpdate>();
    private Set<TreePath> fPropertiesUpdates = makeTreePathSet();
    private Set<IViewerUpdate> fPropertiesUpdatesRunning = new HashSet<IViewerUpdate>();
    private Set<IViewerUpdate> fPropertiesUpdatesCompleted = new HashSet<IViewerUpdate>();
//    private Set<TreePath> fProxyModels = new HashSet<TreePath>();
    private Set<TreePath> fStateUpdates = makeTreePathSet();
    private boolean fContentSequenceStarted;
    private boolean fContentSequenceComplete;
    private boolean fLabelUpdatesStarted;
    private boolean fLabelSequenceComplete;
    private boolean fModelChangedComplete;
    private boolean fStateSaveStarted;
    private boolean fStateSaveComplete;
    private boolean fStateRestoreStarted;
    private boolean fStateRestoreComplete;
    private int fContentUpdatesCounter;
    private int fLabelUpdatesCounter;
    private int fPropertiesUpdatesCounter;
    private int fTimeoutInterval = 60000;
	private long fTimeoutTime;
	
	protected Set<TreePath> makeTreePathSet() {
	    return new HashSet<TreePath>();
	}
	    
	protected <V> Map<TreePath, V> makeTreePathMap() {
	    return new HashMap<TreePath, V>();
	}

	
    public ViewerUpdatesListener(ITreeModelViewer viewer, boolean failOnRedundantUpdates, boolean failOnMultipleModelUpdateSequences) {
        this(viewer);
        setFailOnRedundantUpdates(failOnRedundantUpdates);
        setFailOnMultipleModelUpdateSequences(failOnMultipleModelUpdateSequences);
    }

    public ViewerUpdatesListener() {
        // No viewer to register with.  Client will have to register the listener manually.
    }

	public ViewerUpdatesListener(ITreeModelViewer viewer) {
	    fViewer = viewer;
        fViewer.addLabelUpdateListener(this);
        fViewer.addModelChangedListener(this);
        fViewer.addStateUpdateListener(this);
        fViewer.addViewerUpdateListener(this);
    }

	public void dispose() {
	    if (fViewer != null) {
            fViewer.removeLabelUpdateListener(this);
            fViewer.removeModelChangedListener(this);
            fViewer.removeStateUpdateListener(this);
            fViewer.removeViewerUpdateListener(this);
            fViewer = null;
	    }
	}
	
	
    public void setFailOnRedundantUpdates(boolean failOnRedundantUpdates) {
        fFailOnRedundantUpdates = failOnRedundantUpdates;
    }

    public void setFailOnMultipleModelUpdateSequences(boolean failOnMultipleLabelUpdateSequences) {
        fFailOnMultipleModelUpdateSequences = failOnMultipleLabelUpdateSequences;
    }

    public void setFailOnMultipleLabelUpdateSequences(boolean failOnMultipleLabelUpdateSequences) {
        fFailOnMultipleLabelUpdateSequences = failOnMultipleLabelUpdateSequences;
    }

    /**
     * Sets the the maximum amount of time (in milliseconds) that the update listener 
     * is going to wait. If set to -1, the listener will wait indefinitely. 
     */
    public void setTimeoutInterval(int milis) {
        fTimeoutInterval = milis;
    }
    
    public void reset() {
        fRedundantUpdates.clear();
        fMultipleLabelUpdateSequencesObserved = false;
        fMultipleModelUpdateSequencesObserved = false;
        fHasChildrenUpdatesScheduled.clear();
        fHasChildrenUpdatesRunning.clear();
        fHasChildrenUpdatesCompleted.clear();
        fChildrenUpdatesScheduled.clear();
        fChildrenUpdatesRunning.clear();
        fChildrenUpdatesCompleted.clear();
        fChildCountUpdatesScheduled.clear();
        fChildCountUpdatesRunning.clear();
        fChildCountUpdatesCompleted.clear();
        fLabelUpdates.clear();
        fLabelUpdatesRunning.clear();
        fLabelUpdatesCompleted.clear();
//        fProxyModels.clear();
        fContentSequenceStarted = false;
        fContentSequenceComplete = false;
        fLabelUpdatesStarted = false;
        fLabelSequenceComplete = false;
        fStateSaveStarted = false;
        fStateSaveComplete = false;
        fStateRestoreStarted = false;
        fStateRestoreComplete = false;
        fTimeoutTime = System.currentTimeMillis() + fTimeoutInterval;
        resetModelChanged();
    }
    
    public void resetModelChanged() {
        fModelChangedComplete = false;
    }
    
    public void addHasChildrenUpdate(TreePath path) {
        fHasChildrenUpdatesScheduled.add(path);
    }

    public void removeHasChildrenUpdate(TreePath path) {
        fHasChildrenUpdatesScheduled.remove(path);
    }

    public void addChildCountUpdate(TreePath path) {
        fChildCountUpdatesScheduled.add(path);
    }

    public void removeChildreCountUpdate(TreePath path) {
        fChildCountUpdatesScheduled.remove(path);
    }

    public void addChildreUpdate(TreePath path, int index) {
        Set<Integer> childrenIndexes = fChildrenUpdatesScheduled.get(path);
        if (childrenIndexes == null) {
            childrenIndexes = new TreeSet<Integer>();
            fChildrenUpdatesScheduled.put(path, childrenIndexes);
        }
        childrenIndexes.add(index);
    }

    public void removeChildrenUpdate(TreePath path, int index) {
        Set<Integer> childrenIndexes = fChildrenUpdatesScheduled.get(path);
        if (childrenIndexes != null) {
            childrenIndexes.remove(Integer.valueOf(index));
            if (childrenIndexes.isEmpty()) {
                fChildrenUpdatesScheduled.remove(path);
            }
        }
    }

    public void addLabelUpdate(TreePath path) {
        fLabelUpdates.add(path);
    }

    public void addPropertiesUpdate(TreePath path) {
        fPropertiesUpdates.add(path);
    }

    public void removeLabelUpdate(TreePath path) {
        fLabelUpdates.remove(path);
    }

    public void addStateUpdate(TreePath path) {
        fStateUpdates.add(path);
    }

    public void removeStateUpdate(TreePath path) {
        fStateUpdates.remove(path);
    }

    
    public boolean isFinished() {
        return isFinished(ALL_UPDATES_COMPLETE);
    }
    
    public boolean isTimedOut() {
        return fTimeoutInterval > 0 && fTimeoutTime < System.currentTimeMillis();
    }
    
    public boolean isFinished(int flags) {
        if (isTimedOut()) {
            throw new RuntimeException("Timed Out: " + toString(flags));
        }
        
        if (fFailOnRedundantUpdates && !fRedundantUpdates.isEmpty()) {
            fail("Redundant Updates: " + fRedundantUpdates.toString());
        }
        if (fFailOnMultipleLabelUpdateSequences && !fMultipleLabelUpdateSequencesObserved) {
            fail("Multiple label update sequences detected");
        }
        if (fFailOnMultipleModelUpdateSequences && fMultipleModelUpdateSequencesObserved) {
            fail("Multiple viewer update sequences detected");
        }

        if ( (flags & LABEL_SEQUENCE_COMPLETE) != 0) {
            if (!fLabelSequenceComplete) return false;
        }
        if ( (flags & LABEL_SEQUENCE_STARTED) != 0) {
            if (!fLabelUpdatesStarted) return false;
        }
        if ( (flags & LABEL_UPDATES) != 0) {
            if (!fLabelUpdates.isEmpty()) return false;
        }
        if ( (flags & CONTENT_SEQUENCE_STARTED) != 0) {
            if (!fContentSequenceStarted) return false;
        }
        if ( (flags & CONTENT_SEQUENCE_COMPLETE) != 0) {
            if (!fContentSequenceComplete) return false;
        }
        if ( (flags & HAS_CHILDREN_UPDATES_STARTED) != 0) {
            if (fHasChildrenUpdatesRunning.isEmpty() && fHasChildrenUpdatesCompleted.isEmpty()) return false;
        }
        if ( (flags & HAS_CHILDREN_UPDATES) != 0) {
            if (!fHasChildrenUpdatesScheduled.isEmpty()) return false;
        }
        if ( (flags & CHILD_COUNT_UPDATES_STARTED) != 0) {
            if (fChildCountUpdatesRunning.isEmpty() && fChildCountUpdatesCompleted.isEmpty()) return false;
        }
        if ( (flags & CHILD_COUNT_UPDATES) != 0) {
            if (!fChildCountUpdatesScheduled.isEmpty()) return false;
        }
        if ( (flags & CHILDREN_UPDATES_STARTED) != 0) {
            if (fChildrenUpdatesRunning.isEmpty() && fChildrenUpdatesCompleted.isEmpty()) return false;
        }
        if ( (flags & CHILDREN_UPDATES) != 0) {
            if (!fChildrenUpdatesScheduled.isEmpty()) return false;
        }
        if ( (flags & MODEL_CHANGED_COMPLETE) != 0) {
            if (!fModelChangedComplete) return false;
        }
        if ( (flags & STATE_SAVE_COMPLETE) != 0) {
            if (!fStateSaveComplete) return false;
        }
        if ( (flags & STATE_SAVE_STARTED) != 0) {
            if (!fStateSaveStarted) return false;
        }
        if ( (flags & STATE_RESTORE_COMPLETE) != 0) {
            if (!fStateRestoreComplete) return false;
        }
        if ( (flags & STATE_RESTORE_STARTED) != 0) {
            if (!fStateRestoreStarted) return false;
        }
//        if ( (flags & MODEL_PROXIES_INSTALLED) != 0) {
//            if (fProxyModels.size() != 0) return false;
//        }
        if ( (flags & VIEWER_UPDATES_RUNNING) != 0) {
            if (fContentUpdatesCounter != 0) {
            	return false;
            }
        }
        if ( (flags & LABEL_UPDATES_RUNNING) != 0) {
            if (fLabelUpdatesCounter != 0) {
            	return false;
            }
        }
        if ( (flags & PROPERTY_UPDATES) != 0) {
            if (!fPropertiesUpdates.isEmpty()) return false;
        }
        if ( (flags & PROPERTY_UPDATES_STARTED) != 0) {
            if (fPropertiesUpdatesRunning.isEmpty() && fPropertiesUpdatesCompleted.isEmpty()) return false;
        }
        
        return true;
    }
    
    @Override
    public void updateStarted(IViewerUpdate update) {
        synchronized (this) {
        	fContentUpdatesCounter++;
            if (update instanceof IHasChildrenUpdate) {
                fHasChildrenUpdatesRunning.add(update);
            } if (update instanceof IChildrenCountUpdate) {
                fChildCountUpdatesRunning.add(update);
            } else if (update instanceof IChildrenUpdate) {
                fChildCountUpdatesRunning.add(update);
            } 
        }
    }
    
    @Override
    public void updateComplete(IViewerUpdate update) {
        synchronized (this) {
        	fContentUpdatesCounter--;
        }

        if (!update.isCanceled()) {
            if (update instanceof IHasChildrenUpdate) {
                fHasChildrenUpdatesRunning.remove(update);
                fHasChildrenUpdatesCompleted.add(update);                
                if (!fHasChildrenUpdatesScheduled.remove(update.getElementPath()) && fFailOnRedundantUpdates) {
                    fRedundantUpdates.add(update);
                }
            } if (update instanceof IChildrenCountUpdate) {
                fChildCountUpdatesRunning.remove(update);
                fChildCountUpdatesCompleted.add(update);                
                if (!fChildCountUpdatesScheduled.remove(update.getElementPath()) && fFailOnRedundantUpdates) {
                    fRedundantUpdates.add(update);
                }
            } else if (update instanceof IChildrenUpdate) {
                fChildrenUpdatesRunning.remove(update);
                fChildrenUpdatesCompleted.add(update);                
                
                int start = ((IChildrenUpdate)update).getOffset();
                int end = start + ((IChildrenUpdate)update).getLength();
                
                Set<Integer> childrenIndexes = fChildrenUpdatesScheduled.get(update.getElementPath());
                if (childrenIndexes != null) {
                    for (int i = start; i < end; i++) {
                        childrenIndexes.remove(Integer.valueOf(i));
                    }
                    if (childrenIndexes.isEmpty()) {
                        fChildrenUpdatesScheduled.remove(update.getElementPath());
                    }
                } else if (fFailOnRedundantUpdates) {
                    fRedundantUpdates.add(update);
                }
            } 
        }
    }
    
    @Override
    public void viewerUpdatesBegin() {
        if (fFailOnMultipleModelUpdateSequences && fContentSequenceComplete) {
            fMultipleModelUpdateSequencesObserved = true;
        }
        fContentSequenceStarted = true;
    }
    
    @Override
    public void viewerUpdatesComplete() {
        fContentSequenceComplete = true;
    }

    @Override
    public void labelUpdateComplete(ILabelUpdate update) {
        synchronized (this) {
            fLabelUpdatesRunning.remove(update);
            fLabelUpdatesCompleted.add(update);
        	fLabelUpdatesCounter--;
        }
        if (!fLabelUpdates.remove(update.getElementPath()) && fFailOnRedundantUpdates) {
            fRedundantUpdates.add(update);
        }
    }

    @Override
    public void labelUpdateStarted(ILabelUpdate update) {
        synchronized (this) {
            fLabelUpdatesRunning.add(update);
        	fLabelUpdatesCounter++;
        }
    }

    @Override
    public void labelUpdatesBegin() {
        if (fFailOnMultipleLabelUpdateSequences && fLabelSequenceComplete) {
            fMultipleLabelUpdateSequencesObserved = true;
        }
        fLabelUpdatesStarted = true;
    }

    @Override
    public void labelUpdatesComplete() {
        fLabelSequenceComplete = true;
    }

    @Override
    public void propertiesUpdatesStarted(IPropertiesUpdate[] updates) {
        for (IPropertiesUpdate update : updates) {
            fPropertiesUpdatesRunning.add(update);
            fPropertiesUpdatesCounter++;
        }
    }

    @Override
    public void propertiesUpdateCompleted(IPropertiesUpdate update) {
        synchronized (this) {
            fPropertiesUpdatesRunning.remove(update);
            fPropertiesUpdatesCompleted.add(update);
            fPropertiesUpdatesCounter--;
        }
        if (!fPropertiesUpdates.remove(update.getElementPath()) && fFailOnRedundantUpdates) {
            fRedundantUpdates.add(update);
        }

    }
    
    @Override
    public void modelChanged(IModelDelta delta, IModelProxy proxy) {
        fModelChangedComplete = true;
    }
    
    @Override
    public void stateRestoreUpdatesBegin(Object input) {
        fStateRestoreStarted = true;
    }
    
    @Override
    public void stateRestoreUpdatesComplete(Object input) {
        fStateRestoreComplete = true;
    }
    
    @Override
    public void stateSaveUpdatesBegin(Object input) {
        fStateSaveStarted = true;
    }

    @Override
    public void stateSaveUpdatesComplete(Object input) {
        fStateSaveComplete = true;
    }
    
    @Override
    public void stateUpdateComplete(Object input, IViewerUpdate update) {
    }
    
    @Override
    public void stateUpdateStarted(Object input, IViewerUpdate update) {
    }
    
    private String toString(int flags) {
        StringBuilder buf = new StringBuilder("Viewer Update Listener");

        if (fFailOnRedundantUpdates) {
            buf.append("\n\t");
            buf.append("fRedundantUpdates = ");
            buf.append( toStringViewerUpdatesSet(fRedundantUpdates) );
        }
        if (fFailOnMultipleLabelUpdateSequences) {
            buf.append("\n\t");
            buf.append("fMultipleLabelUpdateSequencesObserved = ").append(fMultipleLabelUpdateSequencesObserved);
        }
        if (fFailOnMultipleModelUpdateSequences) {
            buf.append("\n\t");
            buf.append("fMultipleModelUpdateSequencesObserved = ").append(fMultipleModelUpdateSequencesObserved);
        }
        if ( (flags & LABEL_SEQUENCE_COMPLETE) != 0) {
            buf.append("\n\t");
            buf.append("fLabelSequenceComplete = ").append(fLabelSequenceComplete);
        }
        if ( (flags & LABEL_UPDATES_RUNNING) != 0) {
            buf.append("\n\t");
            buf.append("fLabelUpdatesRunning = ").append(fLabelUpdatesCounter);
        }
        if ( (flags & LABEL_SEQUENCE_STARTED) != 0) {
            buf.append("\n\t");
            buf.append("fLabelUpdatesRunning = ");
            buf.append( toStringViewerUpdatesSet(fLabelUpdatesRunning) );
            buf.append("\n\t");
            buf.append("fLabelUpdatesCompleted = ");
            buf.append( toStringViewerUpdatesSet(fLabelUpdatesCompleted) );
        }
        if ( (flags & LABEL_UPDATES) != 0) {
            buf.append("\n\t");
            buf.append("fLabelUpdates = ");
            buf.append( toString(fLabelUpdates) );
        }
        if ( (flags & CONTENT_SEQUENCE_COMPLETE) != 0) {
            buf.append("\n\t");
            buf.append("fContentSequenceComplete = ").append(fContentSequenceComplete);
        }
        if ( (flags & VIEWER_UPDATES_RUNNING) != 0) {
            buf.append("\n\t");
            buf.append("fContentUpdatesCounter = ").append(fContentUpdatesCounter);
        }
        if ( (flags & HAS_CHILDREN_UPDATES_STARTED) != 0) {
            buf.append("\n\t");
            buf.append("fHasChildrenUpdatesRunning = ");
            buf.append( toStringViewerUpdatesSet(fHasChildrenUpdatesRunning) );
            buf.append("\n\t");
            buf.append("fHasChildrenUpdatesCompleted = ");
            buf.append( toStringViewerUpdatesSet(fHasChildrenUpdatesCompleted) );
        }
        if ( (flags & HAS_CHILDREN_UPDATES) != 0) {
            buf.append("\n\t");
            buf.append("fHasChildrenUpdates = ");
            buf.append( toString(fHasChildrenUpdatesScheduled) );
        }
        if ( (flags & CHILD_COUNT_UPDATES_STARTED) != 0) {
            buf.append("\n\t");
            buf.append("fChildCountUpdatesRunning = ");
            buf.append( toStringViewerUpdatesSet(fChildCountUpdatesRunning) );
            buf.append("\n\t");
            buf.append("fChildCountUpdatesCompleted = ");
            buf.append( toStringViewerUpdatesSet(fChildCountUpdatesCompleted) );
        }
        if ( (flags & CHILD_COUNT_UPDATES) != 0) {
            buf.append("\n\t");
            buf.append("fChildCountUpdates = ");
            buf.append( toString(fChildCountUpdatesScheduled) );
        }
        if ( (flags & CHILDREN_UPDATES_STARTED) != 0) {
            buf.append("\n\t");
            buf.append("fChildrenUpdatesRunning = ");
            buf.append( fChildrenUpdatesRunning );
            buf.append("\n\t");
            buf.append("fChildrenUpdatesCompleted = ");
            buf.append( toStringViewerUpdatesSet(fChildrenUpdatesCompleted) );
        }
        if ( (flags & CHILDREN_UPDATES) != 0) {
            buf.append("\n\t");
            buf.append("fChildrenUpdates = ");
            buf.append( toStringTreePathMap(fChildrenUpdatesScheduled) );
        }
        if ( (flags & MODEL_CHANGED_COMPLETE) != 0) {
            buf.append("\n\t");
            buf.append("fModelChangedComplete = ").append(fModelChangedComplete);
        }
        if ( (flags & STATE_SAVE_COMPLETE) != 0) {
            buf.append("\n\t");
            buf.append("fStateSaveComplete = ").append(fStateSaveComplete);
        }
        if ( (flags & STATE_RESTORE_COMPLETE) != 0) {
            buf.append("\n\t");
            buf.append("fStateRestoreComplete = ").append(fStateRestoreComplete);
        }
//        if ( (flags & MODEL_PROXIES_INSTALLED) != 0) {
//            buf.append("\n\t");
//            buf.append("fProxyModels = ").append(fProxyModels);
//        }
        if ( (flags & PROPERTY_UPDATES_STARTED) != 0) {
            buf.append("\n\t");
            buf.append("fPropertiesUpdatesRunning = ");
            buf.append(toStringViewerUpdatesSet(fPropertiesUpdatesRunning));
            buf.append("\n\t");
            buf.append("fPropertiesUpdatesCompleted = ").append(fPropertiesUpdatesCompleted);
        }
        if ( (flags & PROPERTY_UPDATES) != 0) {
            buf.append("\n\t");
            buf.append("fPropertiesUpdates = ");
            buf.append( toString(fPropertiesUpdates) );
        }
        if (fTimeoutInterval > 0) {
            buf.append("\n\t");
            buf.append("fTimeoutInterval = ").append(fTimeoutInterval);
        }
        return buf.toString();
    }

    private String toString(Set<TreePath> set) {
        if (set.isEmpty()) {
            return "(EMPTY)";
        }
        StringBuilder buf = new StringBuilder();
        for (Iterator<TreePath> itr = set.iterator(); itr.hasNext(); ) {
            buf.append("\n\t\t");
            buf.append(toStringTreePath(itr.next()));
        }
        return buf.toString();
    }

    private String toStringViewerUpdatesSet(Set<IViewerUpdate> set) {
        if (set.isEmpty()) {
            return "(EMPTY)";
        }
        StringBuilder buf = new StringBuilder();
        for (Iterator<IViewerUpdate> itr = set.iterator(); itr.hasNext(); ) {
            buf.append("\n\t\t");
            buf.append(toStringTreePath((itr.next()).getElementPath()));
        }
        return buf.toString();
    }

    private String toStringTreePathMap(Map<TreePath,  Set<Integer>> map) {
        if (map.isEmpty()) {
            return "(EMPTY)";
        }
        StringBuilder buf = new StringBuilder();
        for (Iterator<TreePath> itr = map.keySet().iterator(); itr.hasNext(); ) {
            buf.append("\n\t\t");
            TreePath path = itr.next();
            buf.append(toStringTreePath(path));
            Set<?> updates = map.get(path);
            buf.append(" = ");
            buf.append(updates.toString());
        }
        return buf.toString();
    }
    
    private String toStringTreePath(TreePath path) {
        if (path.getSegmentCount() == 0) {
            return "/";
        }
        StringBuilder buf = new StringBuilder();
        for (int i = 0; i < path.getSegmentCount(); i++) {
            buf.append("/");
            buf.append(path.getSegment(i));
        }
        return buf.toString();
    }
    
    @Override
    public String toString() {
        return toString(ALL_UPDATES_COMPLETE | MODEL_CHANGED_COMPLETE | STATE_RESTORE_COMPLETE | 
            VIEWER_UPDATES_STARTED | LABEL_SEQUENCE_STARTED | PROPERTY_UPDATES | PROPERTY_UPDATES_STARTED);
    }
}


